/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Алексей
 */
package ru.amse.podolsky.fractalarch;

import java.awt.image.*;
import java.io.*;
import javax.imageio.*;

public class Compression {

   //Constants
    private static final int blockSize = IParams.BLOCK_SIZE;
    private static final int domensPerBlock = 4;
    private static final int domenSize = IParams.DOMEN_SIZE;
    private static final int numberOfAfinConversions = 16;
    private static final int numberOfDecompressTurns = 16;
    private static final int maxPixelIntensiv = 255;
    private static final int zeroCharCode = 48;
    
   private final BufferedImage buff;
   
   private final Block[][] bloks;
   
   private final int imageWidth;
   private final int imageHeight;
   private final int imageWidthInBlocks;
   private final int imageHeightInBlocks;
   private final int numberOfDomens;
   
   public Compression(BufferedImage buff, String step) throws IOException {
        this.buff = buff;
        
        this.imageWidth = buff.getWidth();
        this.imageHeight = buff.getHeight();
                       
        BlockLineParams params = new BlockLineParams();
        params = params.getImageLineParamsInBlocks(this.buff, toInt(step));
        
        this.imageWidthInBlocks = params.imageWidthInBlocks;
        this.imageHeightInBlocks = params.imageHeightInBlocks;
        this.numberOfDomens = params.imageWidthInBlocks * params.imageHeightInBlocks * domensPerBlock;
        
        this.bloks = blockFormation(this.buff, toInt(step));
   }
   
   
   
   private Block[][] blockFormation(BufferedImage image, int step){
        
       int numberOfBlocksW = 0;
       int numberOfBlocksH = 0;
       int nonCrossedBlocksW = 0;
       int nonCrossedBlocksH = 0;        
       
       int width = image.getWidth() - blockSize;
       int height = image.getHeight() - blockSize;
       if(step > blockSize){
           step = blockSize;
       }       
       
       
       if(width % step == 0){
            numberOfBlocksW = width / step + 1;
            nonCrossedBlocksW = numberOfBlocksW;
       }else{
           numberOfBlocksW = width / step + 2;
           nonCrossedBlocksW = numberOfBlocksW - 1;
       }
       if(height % step == 0){
           numberOfBlocksH = height / step + 1;
           nonCrossedBlocksH = numberOfBlocksH;
       }else{
           numberOfBlocksH = height / step + 2;
           nonCrossedBlocksH = numberOfBlocksH - 1;
       }
       
       Block[][] localBlocks = new Block[numberOfBlocksW][numberOfBlocksH];
              
       for(int i = 0; i < nonCrossedBlocksW; ++i){
            for(int k = 0; k < nonCrossedBlocksH; ++k){
               localBlocks[i][k] = new Block(image, i * step, k * step); 
            }
        }
        
        if(nonCrossedBlocksW != numberOfBlocksW){
            for(int i = 0; i < nonCrossedBlocksH; ++i){
                localBlocks[nonCrossedBlocksW][i] = new Block(image, image.getWidth() - blockSize, i * step);
            }
        }
        if(nonCrossedBlocksH != numberOfBlocksH){
            for(int i = 0; i < nonCrossedBlocksW; ++i){
                localBlocks[i][nonCrossedBlocksH] = new Block(image, i * step, image.getHeight() - blockSize);
            }
        }
        if(nonCrossedBlocksH != numberOfBlocksH && nonCrossedBlocksW != numberOfBlocksW){
           localBlocks[nonCrossedBlocksW][nonCrossedBlocksH] = new Block(image, image.getWidth() - blockSize, image.getHeight() - blockSize); 
        }
       
       return localBlocks;
   }
   
   public int numberOfBlocks(){       
       return this.imageWidthInBlocks * this.imageHeightInBlocks;
   }
   
   public Block getBlock(int x, int y){
       return this.bloks[x][y];
   }
   
   public Block getBlock(int number){
       return this.getBlock(number % this.imageWidthInBlocks, number / this.imageWidthInBlocks);
   }
   
   public int getMiddleColor(){
       int color = 0;
       for(int i = 0; i < this.imageWidth; ++i){
           for(int k = 0; k < this.imageHeight; ++k){
               color += Math.abs(this.buff.getRGB(i, k) & this.maxPixelIntensiv);
           }
       }
       return color/(this.imageWidth*this.imageHeight);
   }
   
   public int getNumberOfDomens(){
       return this.numberOfDomens;
   }
   
   public int getImageWidth(){
       return this.imageWidth;
   }
   
   public int getImageHeight(){
       return this.imageHeight;
   }
   
   public DomenParams[] compress() throws IOException{
            int numberOfBlocks = this.numberOfBlocks();
            
            Domen[] domens= new Domen[this.numberOfDomens];          
        
            for(int i = 0; i < numberOfBlocks; ++i){
                Domen[] dom = this.getBlock(i).getDomens();
                domens[4 * i] = dom[0];
                domens[4 * i + 1] = dom[1];
                domens[4 * i + 2] = dom[2];
                domens[4 * i + 3] = dom[3];
            }

            int total =  this.numberOfDomens * numberOfBlocks;
            int count = 0;
            for(int i= 0; i < this.numberOfDomens; ++i){
                
                for(int k = 0; k < numberOfBlocks; ++k){
                   count++;
                   float a= (float)(count*1.0)/total;
                   //System.out.print(a+"\r");

                    Domen[] currents = new Domen[this.numberOfAfinConversions];
                     
                    for (int n = 0; n < 4; ++n){
                        int num = n * 4;
                        if(n == 0){
                            currents[num] = this.getBlock(k).compress();
                        }else{
                            currents[num] = currents[num - 4].turn();
                        }
                        currents[num + 1] = currents[num].mirrorHorizontal();
                        currents[num + 2] = currents[num].mirrorVertical();
                        currents[num + 3] = currents[num + 1].mirrorVertical();
                    }
                    
//                    if(i == 0){
//                        GraphicOutput.drowToFile(currents, k);
//                    }
                    
                    int delta = 0; 
                    
                    for(int x = 0; x < this.domenSize; ++x){
                        for(int y = 0; y < this.domenSize; ++y){                            
                            delta += domens[i].getPixel(x, y) - currents[0].getPixel(x, y);                            
                        }
                    }
                    
                    delta /= this.domenSize * this.domenSize;
                    
                    for(int x = 0; x < this.domenSize; ++x){
                        for(int y = 0; y < this.domenSize; ++y){
                            for(int n = 0; n < this.numberOfAfinConversions; n++){
                                int pixel = currents[n].getPixel(x, y) + delta;
//                                if(pixel > this.maxPixelIntensiv){
//                                pixel = this.maxPixelIntensiv;
//                                }else{
//                                    if(pixel  < 0){
//                                        pixel = 0;
//                                    }
//                                }
                                currents[n].setPixel(x, y, pixel);
                            }
                        }
                    }
                    int deltaS = 0;
                    for(int n = 0; n < this.numberOfAfinConversions; ++n){
                        for(int x = 0; x < this.domenSize; ++x){
                            for(int y = 0; y < this.domenSize; ++y){
                                deltaS += Math.abs(domens[i].getPixel(x, y) 
                                    - currents[n].getPixel(x, y));    
                            }
                        }
                        if(deltaS < domens[i].domenParams.testDelta){                            
                            domens[i].domenParams.sameBlockNumber = k;
                            domens[i].domenParams.shift = delta;
                            domens[i].domenParams.numberOfConversion = n;
                            domens[i].domenParams.testDelta = deltaS;
                        }
                        /*
                        if(deltaS < 64){
                            n = this.numberOfAfinConversions;
                        }
                        */
                        deltaS = 0;
                        
                    }
                }
            }
            
            DomenParams[] domensParam= new DomenParams [this.numberOfDomens]; 
            for(int i = 0; i < this.numberOfDomens; ++i){
                domensParam[i] = new DomenParams();
            }
            for(int i = 0; i < this.numberOfDomens; ++i){
                domensParam[i].domenXCoord = domens[i].domenParams.domenXCoord;
                domensParam[i].domenYCoord = domens[i].domenParams.domenYCoord;
                domensParam[i].sameBlockNumber = domens[i].domenParams.sameBlockNumber;
                domensParam[i].shift = domens[i].domenParams.shift;
                domensParam[i].numberOfConversion = domens[i].domenParams.numberOfConversion;
                domensParam[i].testDelta = domens[i].domenParams.testDelta;
            }
            
            return domensParam;
   }
   
   public BufferedImage decompress(DomenParams[] domenParams, int step){
       BufferedImage out = this.buff;
       
       BlockLineParams imageLineParams = new BlockLineParams();
       imageLineParams = imageLineParams.getImageLineParamsInBlocks(out, step);


       for(int n = 0; n < this.numberOfDecompressTurns; n++){
           BufferedImage currentImage = out;
           Block[][] localBlocks = blockFormation(currentImage, step);
           
           for(int i = 0; i < this.numberOfDomens; ++i){
               int xcoord = domenParams[i].domenXCoord;
               int ycoord = domenParams[i].domenYCoord;
               
               int xBlockCoord = domenParams[i].sameBlockNumber % imageLineParams.imageWidthInBlocks;
               int yBlockCoord = domenParams[i].sameBlockNumber / imageLineParams.imageWidthInBlocks;
               
               Domen current = localBlocks[xBlockCoord][yBlockCoord].compress();
               int cur = domenParams[i].numberOfConversion;
               
               for(int x = 0; x < this.domenSize; ++x){
                   for(int y = 0; y < this.domenSize; ++y){
                       int color = current.getPixel(x, y) + domenParams[i].shift;
                       /*
                       if(color > this.maxPixelIntensiv){
                           color = this.maxPixelIntensiv;
                       }else{
                           if(color < 0){
                               color = 0;
                           }
                       }
                       */
                       current.setPixel(x, y, color);
                   }
               }
               //System.out.println(xcoord + "; " + ycoord + "; " + "color: " +current.getMediumColor());

               
               while(cur / (this.numberOfAfinConversions / 4) > 0){
                   current = current.turn();
                   cur /= (this.numberOfAfinConversions / 4);
               }
               switch (cur){
                   case 1:{current = current.mirrorHorizontal(); break;}
                   case 2:{current = current.mirrorVertical(); break;}
                   case 3:{current = current.mirrorHorizontal(); current = current.mirrorVertical(); break;}
               }

               //Sy
               for(int x = 0; x < this.domenSize; ++x){
                   for(int y = 0; y < this.domenSize; ++y){
                       byte c = (byte)current.getPixel(x, y);
                       //System.out.print("c="+c);
                       int color = (c & 0xFF)<<16;
                       color += (c & 0xFF)<<8;
                       color += (c & 0xFF);
                       //System.out.println(" color="+color);
                       currentImage.setRGB(xcoord + x, ycoord + y, (current.getPixel(x, y) << 16) + (current.getPixel(x, y) << 8) + current.getPixel(x, y));
                       //currentImage.setRGB(xcoord + x, ycoord + y, color);
                   }
               }
/*
               ImagesIO tmpIO = new ImagesIO("a_"+n+".bmp");
               try{
                tmpIO.write(currentImage);
               }
               catch(IOException e){}
*/
           }
           out = currentImage;
       }
       
       return out;
   }
   
   private int toInt(String string){
       int length = string.length();
       int out = 0;
       for(int i = 0; i < length; ++i){
           out =  out * 10 + ((int)string.charAt(i) - this.zeroCharCode) ;          
       }
       return out;
   }
}
